package mailx

import (
	"context"
	"encoding/base64"
	"fmt"
	"io"
	"mime/quotedprintable"
	"strings"
	"time"
)

const codepage = "utf-8"

// Message 邮件消息模型
type Message struct {
	To         AddressList
	Cc         AddressList
	Bcc        AddressList
	Subject    string
	Content    string
	Attach     AttachmentList
	From       Address
	ReplyTo    Address
	ReturnPath Address
	Headers    Header
}

func NewMail() *Message {
	return &Message{}
}

type Body func(w io.Writer) (err error)

// Encode 编码邮件并写入
func (msg *Message) Encode(w io.Writer) (err error) {
	hdr := Header{}
	hdr.Set("message-id", generateMessageId())
	hdr.Set("mime-version", "1.0")
	hdr.Set("date", time.Now().Format(time.RFC1123))
	hdr.Set("from", msg.From.String())
	hdr.Set("return-path", msg.ReturnPath.String())
	hdr.Set("reply-to", msg.ReplyTo.String())
	hdr.Set("to", msg.To.String())
	hdr.Set("cc", msg.Cc.String())
	hdr.SetText("subject", msg.Subject)
	msg.Headers.Each(hdr.Set)
	hdr.SetMultipartType("mixed")

	boundary := hdr.Boundary()

	if _, err = w.Write(hdr.HeaderBytes()); err != nil {
		return
	}

	content := "<div>" + strings.ReplaceAll(msg.Content, "\n", "<br />") + "</div>"
	if err = writeText(w, content, "html", boundary); err != nil {
		return
	}

	for _, att := range msg.Attach {
		if err = writeAttach(w, "attachment", att.Name, att.Body, boundary); err != nil {
			return
		}
	}

	if err = closeBoundary(w, boundary); err != nil {
		return
	}

	return
}

// Send 发送邮件
func (msg *Message) Send(ctx context.Context, sOpts ServerOption) (err error) {
	msg.From = Address{Name: sOpts.Name, Address: sOpts.From}
	return Send(ctx, msg.To.Rcpts(), msg.Encode, sOpts)
}

func (msg *Message) Rcpts() (out []string) {
	for _, addr := range []AddressList{msg.To, msg.Cc, msg.Bcc} {
		out = append(out, addr.Rcpts()...)
	}
	return sliceUniq(out)
}

func writeBlock(w io.Writer, hdr *Header, body Body) (err error) {
	if _, err = w.Write(hdr.HeaderBytes()); err != nil {
		err = fmt.Errorf("write header in block: %w", err)
		return
	}
	var wc io.WriteCloser
	switch transferEncoding := strings.ToLower(hdr.GetContentTransferEncoding()); transferEncoding {
	case "quoted-printable":
		wc = quotedprintable.NewWriter(w)
	case "base64":
		wc = base64.NewEncoder(base64.StdEncoding, NewRFC822(w))
	case "":
		wc = closer{Writer: w}
	default:
		err = fmt.Errorf("unhandled transfer encoding: %s", transferEncoding)
		return
	}

	defer func() {
		if _, err = w.Write([]byte{'\r', '\n', '\r', '\n'}); err != nil {
			err = fmt.Errorf("write crlf in block: %w", err)
		}
	}()

	defer wc.Close()
	if err = body(wc); err != nil {
		err = fmt.Errorf("write body in block: %w", err)
		return
	}
	return
}

func writeText(w io.Writer, text, textType, boundary string) (err error) {
	if err = writeBoundary(w, boundary); err != nil {
		return
	}
	return writeBlock(w, createTextHeader(textType), stringWriter(text))
}

func writeAttach(w io.Writer, disp, name string, body Body, boundary string) (err error) {
	if err = writeBoundary(w, boundary); err != nil {
		return
	}
	return writeBlock(w, createAttachmentHeader(disp, name), body)
}
